/*=====================================================
Critical Links
Auto Configuration Server

Authors:
	Rui Eduardo Gouveia Gil - r-gil@critical-links.com

Date: 2010
Version 0.1
=======================================================*/
#include "httpd/DiskFileSender.h"
#include "ACSConfig.h"

namespace acs
{

boost::shared_ptr<DiskFileSender> DiskFileSender::create(DiskFile& file, pion::net::HTTPRequestPtr& request, pion::net::TCPConnectionPtr& tcpConnection, unsigned long maxChunkSize)
{
	return boost::shared_ptr<DiskFileSender>(new DiskFileSender(file,request,tcpConnection,maxChunkSize));
}

DiskFileSender::~DiskFileSender()
{
}

void DiskFileSender::send()
{
	if (diskFile.getFileSize() <= bytesSent)
	{
		writer->send();
		return;
	}

	fileBytesToSend = diskFile.getFileSize() - bytesSent;
	if (maxChunkSize > 0 && fileBytesToSend > maxChunkSize)
		fileBytesToSend = maxChunkSize;

	char* fileContentPtr;
	if (diskFile.hasFileContent())
	{
		fileContentPtr = diskFile.getFileContent() + bytesSent;

	}
	else
	{
		if (! fileStream.is_open())
		{
			fileStream.open(diskFile.getFilePath(), std::ios::in | std::ios::binary);

			if (! fileStream.is_open())
			{
				LOG_ERROR_DATA("Unable to open file: " << diskFile.getFilePath().file_string());
				return;
			}
		}

		if (! contentBuf)
			contentBuf.reset(new char[fileBytesToSend]);

		fileContentPtr = contentBuf.get();
		if (! fileStream.read(contentBuf.get(), fileBytesToSend))
		{
			if (fileStream.gcount() > 0)
			{
				LOG_ERROR_DATA("File size inconsistency: "  << diskFile.getFilePath().file_string());
			}
			else
			{
				LOG_ERROR_DATA("Unable to read file: " << diskFile.getFilePath().file_string());
			}
			return;
		}
	}

	writer->writeNoCopy(fileContentPtr, fileBytesToSend);

	if (bytesSent + fileBytesToSend >= diskFile.getFileSize())
	{
		if (bytesSent > 0)
			writer->sendFinalChunk(boost::bind(&DiskFileSender::handleWrite, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
		else
			writer->send(boost::bind(&DiskFileSender::handleWrite, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
	}
	else
		writer->sendChunk(boost::bind(&DiskFileSender::handleWrite,	shared_from_this(),	boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}

DiskFileSender::DiskFileSender(DiskFile& file, pion::net::HTTPRequestPtr& request, pion::net::TCPConnectionPtr& tcpConn, unsigned long maxSize) : diskFile(file), writer(pion::net::HTTPResponseWriter::create(tcpConn, *request, boost::bind(&pion::net::TCPConnection::finish, tcpConn))), maxChunkSize(maxSize), fileBytesToSend(0), bytesSent(0)
{
	LOG_DEBUG_DATA("Preparing to send file" << (diskFile.hasFileContent() ? " (cached): " : ": ") << diskFile.getFilePath().file_string());

	writer->getResponse().setContentType(diskFile.getMimeType());
	writer->getResponse().addHeader(pion::net::HTTPTypes::HEADER_LAST_MODIFIED, diskFile.getLastModifiedString());

	writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_OK);
	writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_OK);
}

void DiskFileSender::handleWrite(const boost::system::error_code& writeError, std::size_t bytesWritten)
{
	bool finishedSending = true;

	if (writeError)
	{
		writer->getTCPConnection()->setLifecycle(pion::net::TCPConnection::LIFECYCLE_CLOSE);
		LOG_WARN_DATA("Error sending file (" << writeError.message() << ')');
	}
	else
	{
		bytesSent += fileBytesToSend;

		if (bytesSent >= diskFile.getFileSize())
		{
			LOG_DEBUG_DATA("Sent " << (fileBytesToSend < diskFile.getFileSize() ? "file chunk" : "complete file") << " of " << fileBytesToSend << " bytes (finished" << (writer->getTCPConnection()->getKeepAlive() ? ", keeping alive)" : ", closing)") );
		}
		else
		{
			LOG_DEBUG_DATA("Sent file chunk of " << fileBytesToSend << " bytes");
			finishedSending = false;
			writer->clear();
		}
	}

	if (finishedSending)
		writer->getTCPConnection()->finish();
	else
		this->send();
}

}
